import { DragType } from './types'

import type { VModuleDrag, DragEvents, HandlerObject, DragMeta } from './types'

const DropStrategy: Record<
  string,
  Record<string, (data: DragMeta, sourceID: number, targetID: number) => void>
> = {
  [DragType.ModuleSource]: {
    [DragType.ModuleSlot]: handleModuleSourceToModuleSlot
  },
  [DragType.ModuleSlot]: {
    [DragType.ModuleSlot]: handleModuleSlotToModuleSlot,
    [DragType.ModuleSource]: handleModuleSlotToModuleSource,
    [DragType.ModuleSourceContainer]: handleModuleSlotToModuleSourceContainer
  }
}

const dragHandlers: HandlerObject = {
  dragstart(event) {
    const target = event.target as HTMLElement
    target.style.opacity = '0.4'

    const dataset = target.dataset
    event.dataTransfer?.setData('source', `${dataset.id} ${dataset.type}`)
  },
  dragover(event) {
    event.preventDefault()

    const [_id, sourceType] = event.dataTransfer!.getData('source').split(' ')
    const targetType = (event.target as HTMLElement).dataset.type as DragType | undefined
    event.dataTransfer!.dropEffect =
      targetType === undefined ||
      (sourceType === targetType && targetType === DragType.ModuleSource)
        ? 'none'
        : 'move'
  },
  drop(event, data) {
    const [sourceID, sourceType] = event.dataTransfer!.getData('source').split(' ')
    const targetType = (event.target as HTMLElement).dataset.type as DragType
    const targetID = (event.target as HTMLElement).dataset?.id ?? '-1'

    DropStrategy[sourceType]?.[targetType]?.(data, +sourceID, +targetID)
  },
  dragend(event) {
    event.preventDefault()
    ;(event.target as HTMLElement).style.opacity = '1'
  }
}

export const vModuleDrag: VModuleDrag = {
  beforeMount(el, { value }) {
    Object.entries(dragHandlers).forEach(([event, handler]) => {
      el.addEventListener(event as DragEvents, (e) => handler(e, value))
    })
  }
}

function handleModuleSourceToModuleSlot(data: DragMeta, sourceID: number, targetID: number) {
  const slotModule = data.moduleSlots[targetID].module
  slotModule !== undefined && data.moduleSourceList.push(slotModule)

  data.moduleSlots[targetID].module = data.moduleSourceList.splice(
    data.moduleSourceList.findIndex(({ id }) => id === sourceID),
    1
  )[0]
}

function handleModuleSlotToModuleSlot(data: DragMeta, sourceID: number, targetID: number) {
  ;[data.moduleSlots[targetID].module, data.moduleSlots[sourceID].module] = [
    data.moduleSlots[sourceID].module,
    data.moduleSlots[targetID].module
  ]
}

function handleModuleSlotToModuleSource(data: DragMeta, sourceID: number, targetID: number) {
  const sourceModule = data.moduleSlots[sourceID].module!

  data.moduleSlots[sourceID].module = data.moduleSourceList.splice(
    data.moduleSourceList.findIndex(({ id }) => id === targetID),
    1,
    sourceModule
  )[0]
}

function handleModuleSlotToModuleSourceContainer(data: DragMeta, sourceID: number, _: number) {
  data.moduleSourceList.push(data.moduleSlots[sourceID].module!)
  data.moduleSlots[sourceID].module = undefined
}
